// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/test/events_test_utils_x11.h"

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XI2.h>
#include <X11/keysym.h>
#include <stddef.h>

#include "base/logging.h"
#include "base/macros.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"

namespace {

// Converts ui::EventType to state for X*Events.
unsigned int XEventState(int flags)
{
    return ((flags & ui::EF_SHIFT_DOWN) ? ShiftMask : 0) | ((flags & ui::EF_CAPS_LOCK_ON) ? LockMask : 0) | ((flags & ui::EF_CONTROL_DOWN) ? ControlMask : 0) | ((flags & ui::EF_ALT_DOWN) ? Mod1Mask : 0) | ((flags & ui::EF_NUM_LOCK_ON) ? Mod2Mask : 0) | ((flags & ui::EF_MOD3_DOWN) ? Mod3Mask : 0) | ((flags & ui::EF_COMMAND_DOWN) ? Mod4Mask : 0) | ((flags & ui::EF_ALTGR_DOWN) ? Mod5Mask : 0) | ((flags & ui::EF_LEFT_MOUSE_BUTTON) ? Button1Mask : 0) | ((flags & ui::EF_MIDDLE_MOUSE_BUTTON) ? Button2Mask : 0) | ((flags & ui::EF_RIGHT_MOUSE_BUTTON) ? Button3Mask : 0);
}

// Converts EventType to XKeyEvent type.
int XKeyEventType(ui::EventType type)
{
    switch (type) {
    case ui::ET_KEY_PRESSED:
        return KeyPress;
    case ui::ET_KEY_RELEASED:
        return KeyRelease;
    default:
        return 0;
    }
}

// Converts EventType to XI2 event type.
int XIKeyEventType(ui::EventType type)
{
    switch (type) {
    case ui::ET_KEY_PRESSED:
        return XI_KeyPress;
    case ui::ET_KEY_RELEASED:
        return XI_KeyRelease;
    default:
        return 0;
    }
}

int XIButtonEventType(ui::EventType type)
{
    switch (type) {
    case ui::ET_MOUSEWHEEL:
    case ui::ET_MOUSE_PRESSED:
        // The button release X events for mouse wheels are dropped by Aura.
        return XI_ButtonPress;
    case ui::ET_MOUSE_RELEASED:
        return XI_ButtonRelease;
    default:
        NOTREACHED();
        return 0;
    }
}

// Converts Aura event type and flag to X button event.
unsigned int XButtonEventButton(ui::EventType type,
    int flags)
{
    // Aura events don't keep track of mouse wheel button, so just return
    // the first mouse wheel button.
    if (type == ui::ET_MOUSEWHEEL)
        return Button4;

    if (flags & ui::EF_LEFT_MOUSE_BUTTON)
        return Button1;
    if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
        return Button2;
    if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
        return Button3;

    return 0;
}

void InitValuatorsForXIDeviceEvent(XIDeviceEvent* xiev)
{
    int valuator_count = ui::DeviceDataManagerX11::DT_LAST_ENTRY;
    xiev->valuators.mask_len = (valuator_count / 8) + 1;
    xiev->valuators.mask = new unsigned char[xiev->valuators.mask_len];
    memset(xiev->valuators.mask, 0, xiev->valuators.mask_len);
    xiev->valuators.values = new double[valuator_count];
}

XEvent* CreateXInput2Event(int deviceid,
    int evtype,
    int tracking_id,
    const gfx::Point& location)
{
    XEvent* event = new XEvent;
    memset(event, 0, sizeof(*event));
    event->type = GenericEvent;
    event->xcookie.data = new XIDeviceEvent;
    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(event->xcookie.data);
    memset(xiev, 0, sizeof(XIDeviceEvent));
    xiev->deviceid = deviceid;
    xiev->sourceid = deviceid;
    xiev->evtype = evtype;
    xiev->detail = tracking_id;
    xiev->event_x = location.x();
    xiev->event_y = location.y();
    xiev->event = DefaultRootWindow(gfx::GetXDisplay());
    if (evtype == XI_ButtonPress || evtype == XI_ButtonRelease) {
        xiev->buttons.mask_len = 8;
        xiev->buttons.mask = new unsigned char[xiev->buttons.mask_len];
        memset(xiev->buttons.mask, 0, xiev->buttons.mask_len);
    }
    return event;
}

} // namespace

namespace ui {

// XInput2 events contain additional data that need to be explicitly freed (see
// |CreateXInput2Event()|.
void XEventDeleter::operator()(XEvent* event)
{
    if (event->type == GenericEvent) {
        XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(event->xcookie.data);
        if (xiev) {
            delete[] xiev->valuators.mask;
            delete[] xiev->valuators.values;
            delete[] xiev->buttons.mask;
            delete xiev;
        }
    }
    delete event;
}

ScopedXI2Event::ScopedXI2Event() { }
ScopedXI2Event::~ScopedXI2Event() { }

void ScopedXI2Event::InitKeyEvent(EventType type,
    KeyboardCode key_code,
    int flags)
{
    XDisplay* display = gfx::GetXDisplay();
    event_.reset(new XEvent);
    memset(event_.get(), 0, sizeof(XEvent));
    event_->type = XKeyEventType(type);
    CHECK_NE(0, event_->type);
    event_->xkey.serial = 0;
    event_->xkey.send_event = 0;
    event_->xkey.display = display;
    event_->xkey.time = 0;
    event_->xkey.window = 0;
    event_->xkey.root = 0;
    event_->xkey.subwindow = 0;
    event_->xkey.x = 0;
    event_->xkey.y = 0;
    event_->xkey.x_root = 0;
    event_->xkey.y_root = 0;
    event_->xkey.state = XEventState(flags);
    event_->xkey.keycode = XKeyCodeForWindowsKeyCode(key_code, flags, display);
    event_->xkey.same_screen = 1;
}

void ScopedXI2Event::InitMotionEvent(const gfx::Point& location,
    const gfx::Point& root_location,
    int flags)
{
    XDisplay* display = gfx::GetXDisplay();
    event_.reset(new XEvent);
    memset(event_.get(), 0, sizeof(XEvent));
    event_->type = MotionNotify;
    event_->xmotion.serial = 0;
    event_->xmotion.send_event = 0;
    event_->xmotion.display = display;
    event_->xmotion.time = 0;
    event_->xmotion.window = 0;
    event_->xmotion.root = 0;
    event_->xkey.subwindow = 0;
    event_->xmotion.x = location.x();
    event_->xmotion.y = location.y();
    event_->xmotion.x_root = root_location.x();
    event_->xmotion.y_root = root_location.y();
    event_->xmotion.state = XEventState(flags);
    event_->xmotion.same_screen = 1;
}

void ScopedXI2Event::InitGenericKeyEvent(int deviceid,
    int sourceid,
    EventType type,
    KeyboardCode key_code,
    int flags)
{
    event_.reset(
        CreateXInput2Event(deviceid, XIKeyEventType(type), 0, gfx::Point()));
    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
    CHECK_NE(0, xievent->evtype);
    XDisplay* display = gfx::GetXDisplay();
    event_->xgeneric.display = display;
    xievent->display = display;
    xievent->mods.effective = XEventState(flags);
    xievent->detail = XKeyCodeForWindowsKeyCode(key_code, flags, display);
    xievent->sourceid = sourceid;
}

void ScopedXI2Event::InitGenericButtonEvent(int deviceid,
    EventType type,
    const gfx::Point& location,
    int flags)
{
    event_.reset(CreateXInput2Event(deviceid,
        XIButtonEventType(type), 0, gfx::Point()));
    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
    xievent->mods.effective = XEventState(flags);
    xievent->detail = XButtonEventButton(type, flags);
    xievent->event_x = location.x();
    xievent->event_y = location.y();
    XISetMask(xievent->buttons.mask, xievent->detail);
    // Setup an empty valuator list for generic button events.
    SetUpValuators(std::vector<Valuator>());
}

void ScopedXI2Event::InitGenericMouseWheelEvent(int deviceid,
    int wheel_delta,
    int flags)
{
    InitGenericButtonEvent(deviceid, ui::ET_MOUSEWHEEL, gfx::Point(), flags);
    XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
    xievent->detail = wheel_delta > 0 ? Button4 : Button5;
}

void ScopedXI2Event::InitScrollEvent(int deviceid,
    int x_offset,
    int y_offset,
    int x_offset_ordinal,
    int y_offset_ordinal,
    int finger_count)
{
    event_.reset(CreateXInput2Event(deviceid, XI_Motion, 0, gfx::Point()));

    Valuator valuators[] = {
        Valuator(DeviceDataManagerX11::DT_CMT_SCROLL_X, x_offset),
        Valuator(DeviceDataManagerX11::DT_CMT_SCROLL_Y, y_offset),
        Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_X, x_offset_ordinal),
        Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_Y, y_offset_ordinal),
        Valuator(DeviceDataManagerX11::DT_CMT_FINGER_COUNT, finger_count)
    };
    SetUpValuators(
        std::vector<Valuator>(valuators, valuators + arraysize(valuators)));
}

void ScopedXI2Event::InitFlingScrollEvent(int deviceid,
    int x_velocity,
    int y_velocity,
    int x_velocity_ordinal,
    int y_velocity_ordinal,
    bool is_cancel)
{
    event_.reset(CreateXInput2Event(deviceid, XI_Motion, deviceid, gfx::Point()));

    Valuator valuators[] = {
        Valuator(DeviceDataManagerX11::DT_CMT_FLING_STATE, is_cancel ? 1 : 0),
        Valuator(DeviceDataManagerX11::DT_CMT_FLING_Y, y_velocity),
        Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_Y, y_velocity_ordinal),
        Valuator(DeviceDataManagerX11::DT_CMT_FLING_X, x_velocity),
        Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_X, x_velocity_ordinal)
    };

    SetUpValuators(
        std::vector<Valuator>(valuators, valuators + arraysize(valuators)));
}

void ScopedXI2Event::InitTouchEvent(int deviceid,
    int evtype,
    int tracking_id,
    const gfx::Point& location,
    const std::vector<Valuator>& valuators)
{
    event_.reset(CreateXInput2Event(deviceid, evtype, tracking_id, location));

    // If a timestamp was specified, setup the event.
    for (size_t i = 0; i < valuators.size(); ++i) {
        if (valuators[i].data_type == DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP) {
            SetUpValuators(valuators);
            return;
        }
    }

    // No timestamp was specified. Use |ui::EventTimeForNow()|.
    std::vector<Valuator> valuators_with_time = valuators;
    valuators_with_time.push_back(
        Valuator(DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP,
            (ui::EventTimeForNow()).InMicroseconds()));
    SetUpValuators(valuators_with_time);
}

void ScopedXI2Event::SetUpValuators(const std::vector<Valuator>& valuators)
{
    CHECK(event_.get());
    CHECK_EQ(GenericEvent, event_->type);
    XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(event_->xcookie.data);
    InitValuatorsForXIDeviceEvent(xiev);
    ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
    for (size_t i = 0; i < valuators.size(); ++i) {
        manager->SetValuatorDataForTest(xiev, valuators[i].data_type,
            valuators[i].value);
    }
}

void SetUpTouchPadForTest(int deviceid)
{
    std::vector<int> device_list;
    device_list.push_back(deviceid);

    TouchFactory::GetInstance()->SetPointerDeviceForTest(device_list);
    ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
    manager->SetDeviceListForTest(std::vector<int>(), device_list,
        std::vector<int>());
}

void SetUpTouchDevicesForTest(const std::vector<int>& devices)
{
    TouchFactory::GetInstance()->SetTouchDeviceForTest(devices);
    ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
    manager->SetDeviceListForTest(devices, std::vector<int>(),
        std::vector<int>());
}

void SetUpPointerDevicesForTest(const std::vector<int>& devices)
{
    TouchFactory::GetInstance()->SetPointerDeviceForTest(devices);
    ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
    manager->SetDeviceListForTest(std::vector<int>(), std::vector<int>(),
        devices);
}

} // namespace ui
